package bat.iog.hytool.ftp;


import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * @Description: JAVA 操作 FTP 文件服务器的工具类
 *
 * @Date: 2022/2/14
 * @Author: jiangXueZhi
 */
public class FTPUtils {
    private static final Logger LOGGER = LoggerFactory.getLogger(FTPUtils.class);

    /**
     * 获取FTPClient对象
     *
     * @param ftpHost     服务器IP
     * @param ftpPort     服务器端口号
     * @param ftpUserName 用户名
     * @param ftpPassword 密码
     * @return FTPClient
     */
    public static FTPClient getFTPClient(String ftpHost, int ftpPort, String ftpUserName, String ftpPassword) {
        FTPClient ftp = null;
        try {
            ftp = new FTPClient();
            // 连接FPT服务器,设置IP及端口
            ftp.connect(ftpHost, ftpPort);
            // 设置用户名和密码
            ftp.login(ftpUserName, ftpPassword);
            // 设置连接超时时间,5000毫秒
            ftp.setConnectTimeout(5000);
            // 设置中文编码集，防止中文乱码
            ftp.setControlEncoding("UTF-8");
            if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
                LOGGER.info("未连接到FTP，用户名或密码错误");
                ftp.disconnect();
            } else {
                LOGGER.info("FTP连接成功");
            }
        } catch (SocketException e) {
            e.printStackTrace();
            LOGGER.info("FTP的IP地址可能错误，请正确配置");
        } catch (IOException e) {
            e.printStackTrace();
            LOGGER.info("FTP的端口错误,请正确配置");
        }
        return ftp;
    }

    /**
     * 关闭FTP
     *
     * @param ftp ftp客户端
     * @return 关闭
     */
    public static boolean closeFTP(FTPClient ftp) {
        try {
            ftp.logout();
        } catch (Exception e) {
            LOGGER.error("FTP关闭失败");
        } finally {
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                } catch (IOException ioe) {
                    LOGGER.error("FTP关闭失败");
                }
            }
        }
        return false;
    }

    /* ---------------------------- FTP 基操 start ---------------------------- */

    /**
     * 下载FTP下指定文件
     *
     * @param ftp      ftp客户端
     * @param filePath FTP文件路径
     * @param fileName 文件名
     * @param downPath 下载保存的目录
     * @param deleteLocalFile true：删除本地文件
     * @return FTP下指定文件
     */
    public static boolean downLoadFTP(FTPClient ftp, String filePath, String fileName, String downPath, boolean deleteLocalFile) {
        // 默认失败
        boolean flag = false;
        try {
            // 跳转到文件目录
            ftp.changeWorkingDirectory(filePath);
            // 获取目录下文件集合
            ftp.enterLocalPassiveMode();
            FTPFile[] files = ftp.listFiles();
            for (FTPFile file : files) {
                // 取得指定文件并下载
                if (file.getName().equals(fileName)) {
                    File downFile = new File(downPath + "/" + file.getName());
                    OutputStream out = new FileOutputStream(downFile);
                    // 绑定输出流下载文件,需要设置编码集，不然可能出现文件为空的情况
                    flag = ftp.retrieveFile(new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1), out);
                    // 下载成功后删除文件
                    if (deleteLocalFile) {
                        ftp.deleteFile(new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
                    }
                    out.flush();
                    out.close();
                    if (flag) {
                        LOGGER.info("下载成功");
                    } else {
                        LOGGER.error("下载失败");
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("下载失败");
        }
        return flag;
    }

    /**
     * FTP文件上传
     *
     * @param ftp      ftp客户端
     * @param filePath 源文件夹地址
     * @param ftpPath  目标文件夹地址
     * @return 上传成功与否
     */
    public static boolean uploadFile(FTPClient ftp, String filePath, String ftpPath) {
        boolean flag = false;
        InputStream in = null;
        try {
            // 设置PassiveMode传输
            ftp.enterLocalPassiveMode();
            // 设置二进制传输，使用BINARY_FILE_TYPE，ASC容易造成文件损坏
            ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
            // 判断FPT目标文件夹是否存在 不存在则创建
            if (!ftp.changeWorkingDirectory(ftpPath)) {
                ftp.makeDirectory(ftpPath);
            }
            // 跳转目标目录
            ftp.changeWorkingDirectory(ftpPath);

            // 上传文件
            File file = new File(filePath);
            in = new FileInputStream(file);
            String tempName = ftpPath + "/" + file.getName();
            flag = ftp.storeFile(new String(tempName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1), in);
            if (flag) {
                LOGGER.info("上传成功");
            } else {
                LOGGER.error("上传失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("上传失败");
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return flag;
    }

    /**
     * FPT上文件的复制
     *
     * @param ftp      ftp客户端
     * @param olePath  原文件地址
     * @param newPath  新保存地址
     * @param fileName 文件名
     * @return 复制成功与否
     */
    public static boolean copyFile(FTPClient ftp, String olePath, String newPath, String fileName) {
        boolean flag = false;
        try {
            // 跳转到文件目录
            ftp.changeWorkingDirectory(olePath);
            // 设置连接模式，不设置会获取为空
            ftp.enterLocalPassiveMode();
            // 获取目录下文件集合
            FTPFile[] files = ftp.listFiles();
            ByteArrayInputStream in;
            ByteArrayOutputStream out;
            for (FTPFile file : files) {
                // 取得指定文件并下载
                if (file.getName().equals(fileName)) {
                    // 读取文件，使用下载文件的方法把文件写入内存,绑定到out流上
                    out = new ByteArrayOutputStream();
                    ftp.retrieveFile(new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1), out);
                    in = new ByteArrayInputStream(out.toByteArray());
                    // 创建新目录
                    ftp.makeDirectory(newPath);
                    // 文件复制，先读，再写
                    // 二进制
                    ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
                    flag = ftp.storeFile(newPath + "/" + (new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)), in);
                    out.flush();
                    out.close();
                    in.close();
                    if (flag) {
                        LOGGER.info("转存成功");
                    } else {
                        LOGGER.error("复制失败");
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("复制失败");
        }
        return flag;
    }

    /**
     * 实现文件夹的移动，这里做的是一个文件夹下的所有内容移动到新的文件，
     * 如果要做指定文件移动，加个判断判断文件名
     * 如果不需要移动，只是需要文件重命名，可以使用ftp.rename(oleName,newName)
     *
     * @param ftp     ftp 客户端
     * @param oldPath 文件源地址
     * @param newPath 文件目标地址
     * @return 移动成功与否
     */
    public static boolean moveDirectory(FTPClient ftp, String oldPath, String newPath) {
        boolean flag = false;
        try {
            ftp.changeWorkingDirectory(oldPath);
            ftp.enterLocalPassiveMode();
            // 获取文件数组
            FTPFile[] files = ftp.listFiles();
            // 新文件夹不存在则创建
            if (!ftp.changeWorkingDirectory(newPath)) {
                ftp.makeDirectory(newPath);
            }
            //回到原有工作目录
            ftp.changeWorkingDirectory(oldPath);
            for (FTPFile file : files) {
                // 转存目录
                flag = ftp.rename(new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1),
                        newPath + "/" + new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
                if (flag) {
                    LOGGER.info(file.getName() + "移动成功");
                } else {
                    LOGGER.error(file.getName() + "移动失败");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("移动文件失败");
        }
        return flag;
    }

    /**
     * 移动文件
     *
     * @param ftp xx
     * @param oldPath xx
     * @param newPath xx
     * @return xx
     */
    public static boolean moveFile(FTPClient ftp, String oldPath, String newPath) {
        boolean flag = false;
        try {
            ftp.changeWorkingDirectory(oldPath);
            ftp.enterLocalPassiveMode();
            // 获取文件数组
            FTPFile[] files = ftp.listFiles();
            // 新文件夹不存在则创建
            if (!ftp.changeWorkingDirectory(newPath)) {
                ftp.makeDirectory(newPath);
            }
            //回到原有工作目录
            ftp.changeWorkingDirectory(oldPath);
            for (FTPFile file : files) {
                if (file.isFile()) {
                    // 转存目录
                    flag = ftp.rename(new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1),
                            newPath + "/" + new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
                    if (flag) {
                        LOGGER.info(file.getName() + "移动成功");
                    } else {
                        LOGGER.error(file.getName() + "移动失败");
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("移动文件失败");
        }
        return flag;
    }

    /**
     * 删除FTP上指定文件夹下文件及其子文件方法，添加了对中文目录的支持
     *
     * @param ftp       ftp 客户端
     * @param ftpFolder 需要删除的文件夹
     * @return 删除成功与否
     */
    public static boolean deleteByFolder(FTPClient ftp, String ftpFolder) {
        boolean flag = false;
        try {
            ftp.changeWorkingDirectory(new String(ftpFolder.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            ftp.enterLocalPassiveMode();
            FTPFile[] files = ftp.listFiles();
            for (FTPFile file : files) {
                // 判断为文件则删除
                if (file.isFile()) {
                    ftp.deleteFile(new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
                }
                // 判断是文件夹
                if (file.isDirectory()) {
                    String childPath = ftpFolder + "/" + file.getName();
                    // 递归删除子文件夹
                    deleteByFolder(ftp, childPath);
                }
            }
            // 循环完成后删除文件夹
            flag = ftp.removeDirectory(new String(ftpFolder.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            if (flag) {
                LOGGER.info(ftpFolder + "文件夹删除成功");
            } else {
                LOGGER.error(ftpFolder + "文件夹删除成功");
            }
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("删除失败");
        }
        return flag;
    }

    /**
     * 遍历解析文件夹下所有文件
     *
     * @param folderPath 需要解析的的文件夹
     * @param ftp        ftp客户端
     * @return 遍历文件的内容
     */
    public static List<Map<String, Object>> readFileByFolder(FTPClient ftp, String folderPath) {
        try {
            ftp.changeWorkingDirectory(new String(folderPath.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            // 设置FTP连接模式
            ftp.enterLocalPassiveMode();
            // 获取指定目录下文件文件对象集合
            FTPFile[] files = ftp.listFiles();
            InputStream in;
            BufferedReader reader;
            List<Map<String, Object>> fileContentList = new LinkedList<>(); // 文件内容集合
            for (FTPFile file : files) {
                // 判断为txt文件则解析
                if (file.isFile()) {
                    String fileName = file.getName();
                    if (fileName.endsWith(".txt")) {
                        in = ftp.retrieveFileStream(new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
                        reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
                        String temp;
                        StringBuilder buffer = new StringBuilder();
                        while ((temp = reader.readLine()) != null) {
                            buffer.append(temp);
                        }
                        reader.close();
                        in.close();
                        // ftp.retrieveFileStream使用了流，需要释放一下，不然会返回空指针
                        ftp.completePendingCommand();
                        // 这里就把一个txt文件完整解析成了个字符串，就可以调用实际需要操作的方法
                        Map<String, Object> map = new HashMap<>();
                        map.put("fileName", fileName);
                        map.put("fileContent", buffer.toString());
                        fileContentList.add(map);
                    }
                }
                // 判断为文件夹，递归
                if (file.isDirectory()) {
                    String path = folderPath + "/" + file.getName();
                    readFileByFolder(ftp, path);
                }
            }
            return fileContentList;
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("文件解析失败");
        }
        return null;
    }

    /**
     * 获取文件夹下的单个文件
     *
     * @param ftp ftp客户端
     * @param folderPath 需要解析的的文件夹，其中有且仅有一个文件
     * @param destination 文件写入的本地地址
     * @return 流 + fileName
     */
    public static File readSingleFileByFolder(FTPClient ftp, String folderPath, String destination) {
        InputStream in = null;
        String fileName = null;
        try {
            ftp.changeWorkingDirectory(new String(folderPath.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            // 设置FTP连接模式
            ftp.enterLocalPassiveMode();
            // 获取指定目录下文件文件对象集合
            FTPFile[] files = ftp.listFiles();
            for (FTPFile file : files) {
                // 判断为文件则解析
                if (file.isFile()) {
                    in = ftp.retrieveFileStream(new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
                    fileName = file.getName();
                    // ftp.retrieveFileStream使用了流，需要释放一下，不然会返回空指针
                    ftp.completePendingCommand();
                    break; // 目录中有且仅有一个文件
                }
            }

            if (in != null) {
                //return FileUtils.getFile(destination + fileName, in);
            }
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("文件解析失败");
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    
    
    public static class ReadSingleFileResult{
        private InputStream inputStream;
        private String fileName;
    }

    /* ---------------------------- FTP 基操 end ----------------------------*/

    /* ---------------------------- FTP 上传与下载的快捷操作 start ----------------------------*/

    /**
     * 向ftp写文件(数据)
     *
     * @param fileContent 要写入的文件内容
     * @param userName    ftp登录用户名
     * @param password    ftp登录密码
     * @param host        ftp的IP地址
     * @param port        ftp端口号
     * @param fileName    创建的文件
     * @param ftpPath     指定写入FTP的目录
     */
    public static void uploadFile(String fileContent, String userName, String password, String host, int port, String fileName, String ftpPath) {
        FTPClient ftpClient = new FTPClient();
        try {
            InputStream in;
            // 1.输入流
            in = new ByteArrayInputStream(fileContent.getBytes());
            // 2.连接服务器
            ftpClient.connect(host, port);
            // 3.登录ftp
            ftpClient.login(userName, password);
            // 4.指定写入Ftp的目录
            ftpClient.changeWorkingDirectory(ftpPath);
            // 5.写操作
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            ftpClient.storeFile(new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1), in);
            in.close();
            LOGGER.info("已上传");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ftpClient.isConnected()) {
                try {
                    ftpClient.disconnect();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * ftp下载数据
     *
     * @param userName     ftp登录用户名
     * @param userPassword ftp登录密码
     * @param host         ftp的IP地址
     * @param port         ftp的端口号
     * @param fileName     创建的文件
     * @param ftpPath      指定FTP的目录
     * @param localPath    指定本地写入文件的地址
     */
    public static void downLoadFile(String userName, String userPassword, String host, int port, String fileName, String ftpPath, String localPath) {
        FTPClient ftp = new FTPClient();
        try {
            int reply;
            //1.连接服务器
            ftp.connect(host, port);
            //2.登录服务器 如果采用默认端口，可以使用ftp.connect(url)的方式直接连接FTP服务器
            ftp.login(userName, userPassword);
            //3.判断登陆是否成功
            reply = ftp.getReplyCode();
            if (!FTPReply.isPositiveCompletion(reply)) {
                LOGGER.info("未连接到FTP，用户名或密码错误");
                ftp.disconnect();
            } else {
                LOGGER.info("FTP连接成功");
            }
            //4.指定要下载的目录
            ftp.changeWorkingDirectory(ftpPath); // 转移到FTP服务器目录
            //5.遍历下载的目录
            FTPFile[] fs = ftp.listFiles();
            for (FTPFile ff : fs) {
                //解决中文乱码问题，两次解码
                byte[] bytes = ff.getName().getBytes(StandardCharsets.ISO_8859_1);
                String fn = new String(bytes, StandardCharsets.UTF_8);
                if (fn.equals(fileName)) {
                    //6.写操作，将其写入到本地文件中
                    File localFile = new File(localPath + ff.getName());
                    OutputStream is = new FileOutputStream(localFile);
                    ftp.retrieveFile(ff.getName(), is);
                    is.close();
                }
            }
            ftp.logout();
            LOGGER.info("已下载");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /* ---------------------------- FTP 上传与下载的快捷操作 end ----------------------------*/
}